package com.typhoon.spring_shiro.controller.interceptor;

import java.io.BufferedReader;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import com.alibaba.fastjson.JSON;
import com.typhoon.spring_shiro.annotation.RequireCheckRange;
import com.typhoon.spring_shiro.constants.PermissionConstants;
import com.typhoon.spring_shiro.constants.PermissionEntityError;
import com.typhoon.spring_shiro.utils.redis.RedisKeyUtils;
import com.typhoon.spring_shiro.utils.redis.RedisKeyUtils.Model;
import com.typhoon.spring_shiro.utils.redis.RedisUtils;

/**
 * 数据范围校验拦截器
 * 
 * @author Typhoon
 * @date 2017-06-06 20:48
 * @since V2.0
 */
public class CheckRangeInterceptor extends AbstractCustomInterceptor implements HandlerInterceptor {

	private static final Logger LOGGER = LoggerFactory.getLogger(CheckRangeInterceptor.class);

//	@Resource
//	private WorkerOfRoleService workerOfRoleService;

	@SuppressWarnings("unchecked")
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

		HandlerMethod handlerMethod = (HandlerMethod) handler;
		Method method = handlerMethod.getMethod();
		boolean isMethodAnnoted = method.isAnnotationPresent(RequireCheckRange.class);
		if (!isMethodAnnoted) {// 没有加注解不需要拦截
			return true;
		}

		// ①把请求参数(json)解析成map
		Map<String, Object> param = readJSONString(request);
		LOGGER.debug("请求参数-{}", Objects.toString(param));

		// ②获取方法注解并分解注解参数
		RequireCheckRange requireCheckRange = method.getAnnotation(RequireCheckRange.class);
		if (requireCheckRange == null) {
			Class<?> cls = handlerMethod.getBeanType();
			requireCheckRange = cls.getAnnotation(RequireCheckRange.class);
		}
		if (StringUtils.isEmpty(requireCheckRange.action()) || ArrayUtils.isEmpty(requireCheckRange.rangeType())) {
			return true;
		}
		String permission = requireCheckRange.action();
		String[] rangeTypes = requireCheckRange.rangeType();
		LOGGER.debug("操作类型:{},数据范围类型:{}", permission, ArrayUtils.toString(rangeTypes));

		// ③检查用户是否在客户端操作了某页面元素对应的数据范围
		ContainsRangeType result = checkRequestContainsRangeType(param, rangeTypes);
		if (null == result || !result.isContains()) {// 如果没有匹配到参数范围类型,跳过拦截处理(此操作存在数据范围校验,但是用户并没有操作)
			return true;
		}
		String rangeType = result.getRangeType();

		// ④从sid解析workerId
		Long workerId = this.decryptWorkerId(request, response);
		if (null == workerId) {
			return false;
		}

		// ⑤检查当前用户是否拥有此操作(注解需要带有操作编码,操作和数据范围、类型有对应关系,如果不传操作编码,数据类型和范围是字典级别,无法确定对应关系)
		String key = RedisKeyUtils.generateKey(Model.PERMISSION, workerId, PermissionConstants.ACTION_PRE);
		Set<String> actions = null;
		try {
			actions = RedisUtils.get(key, Set.class);
		} catch (Exception e) {
			LOGGER.error("从redis拉取workerId:" + workerId + "的权限失败", e);
			return false;
		}
		if (null == actions || actions.isEmpty()) {// 用户尚未登录(没有操作权限)
			this.putParamResult(response, PermissionEntityError.UNAUTHORIZED_OP);
			return false;
		}
		if (!actions.contains(permission)) {// 用户拥有操作集合不包含此操作
			LOGGER.debug("workerId:{}对应权限集:{}不包含操作:{}", workerId, actions.toString(), permission);
			this.putParamResult(response, PermissionEntityError.UNAUTHORIZED_OP);
			return false;
		}

		// ⑥从数据库拉取该用户拥有的数据范围操作
		//RoleActionRangeResp resp = workerOfRoleService.loadActionRange(permission, workerId);// 根据操作编码和工号从数据库拉取类型和范围
	//	LOGGER.debug("workerId:{}数据库中的数据范围响应:{}", workerId, JSON.toJSONString(resp));
//		if (null == resp || !resp.isSuccess()) {// 不存在
//			this.putParamResult(response, PermissionEntityError.UNAUTHORIZED_OP);
//			return false;
//		}
//		List<ActionRangeTypeDto> types = resp.getTypes();
//		if (CollectionUtils.isEmpty(types)) {
//			this.putParamResult(response, PermissionEntityError.UNAUTHORIZED_OP);
//			return false;
//		}

		// ⑦和客户端传来需要操作的数据范围比较
		// TODO
		// 需要前端传来
		String rangeValues = String.valueOf(param.get(rangeType));
		if (StringUtils.isEmpty(rangeValues)) {// 前端传来数据范围空值,不允许
			LOGGER.debug("客户端传来rangeType:{}对应数据范围为空值", rangeType);
			this.putParamResult(response, PermissionEntityError.UNAUTHORIZED_OP);
			return false;
		}
		// 拿注解上数据范围类型和数据库比较
		//ActionRangeTypeDto type = null;
		boolean containsType = false;
//		for (int i = 0, size = types.size(); i < size; i++) {
//			type = types.get(i);
//			if (rangeType.equals(type.getTypeCode())) {
//				containsType = true;
//				break;
//			}
//		}
		if (!containsType) {// 数据库中此角色对应操作不包含此数据范围类型
			LOGGER.debug("workerId:{}对应数据范围类型不包含rangeType:{}",rangeType);
			this.putParamResult(response, PermissionEntityError.UNAUTHORIZED_OP);
			return false;
		}
	//	Set<String> valueSet = type.getRangeValues();
	//	LOGGER.debug("workerId:{}请求数据范围:{},拥有操作范围:{}",workerId,rangeValues,valueSet.toString());
//		if (!valueSet.containsAll(Arrays.asList(rangeValues.split(",")))) {// 数据库中此角色对应操作不全包含自数据范围
//			this.putParamResult(response, PermissionEntityError.UNAUTHORIZED_OP);
//			return false;
//		}
		return true;
	}

	/**
	 * 把请求参数解析成map对象
	 * 
	 * @author Typhoon
	 * @date 2017-06-08 17:26
	 * @since V2.0
	 * @param request
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Map<String, Object> readJSONString(HttpServletRequest request) {
		StringBuffer json = new StringBuffer();
		String line = null;
		Map<String, Object> map = null;
		try {
			BufferedReader reader = request.getReader();
			while ((line = reader.readLine()) != null) {
				json.append(line);
			}
			map = JSON.parseObject(json.toString(), Map.class);
		} catch (Exception e) {
			LOGGER.error("解析参数异常", e);
			return null;
		}
		return map;
	}

	/**
	 * 检查操作是否包含数据范围校验
	 * 
	 * @author Typhoon
	 * @date 2017-06-08 17:49
	 * @since V2.0
	 * @param param
	 * @param rangeType
	 * @return
	 */
	private ContainsRangeType checkRequestContainsRangeType(Map<String, Object> param, String[] rangeType) {
		ContainsRangeType result = new ContainsRangeType();
		if (MapUtils.isEmpty(param)) {
			return result.notContain();
		}
		String type = null;
		for (int i = 0, length = rangeType.length; i < length; i++) {
			type = rangeType[i];
			if (param.containsKey(type)) {
				return result.doContain(type);
			}
		}
		return result.notContain();
	}

	/**
	 * 校验结果
	 * 
	 * @author Typhoon
	 *
	 */
	class ContainsRangeType {
		private boolean contains;
		private String rangeType;

		public boolean isContains() {
			return contains;
		}

		public ContainsRangeType doContain(String type) {
			this.contains = true;
			this.rangeType = type;
			return this;
		}

		public ContainsRangeType notContain() {
			this.contains = false;
			return this;
		}

		public void setContains(boolean contains) {
			this.contains = contains;
		}

		public String getRangeType() {
			return rangeType;
		}

		public void setRangeType(String rangeType) {
			this.rangeType = rangeType;
		}
	}

}
